package com.skywaregroup.arenal;

import java.util.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;

import org.hibernate.*;
import org.hibernate.criterion.*;


//Author: 
//Mikhail Kalatchev, SKYWARE Group, 2012-2014.
//--------- Convention ----------------------------------
//GET       /foo                -> list all records
//GET       /foo/<pk>           -> view record
//POST      /foo                -> create new record
//PUT       /foo/<pk>           -> update record
//DELETE    /foo/<pk>           -> delete record
//---------- This implementation ------------------------
//POST      /publishers         -> create new registration and send it back (with token)
//GET       /publishers/{id}    -> view registration corresponding to existing id and token
//PUT       /publishers/{id}    -> update registration with existing id and token
//DELETE    /publishers/{id}    -> invalidate registration corresponding to existing id token


@Path("/publishers")
@Produces (MediaType.APPLICATION_XML + ";charset=utf-8") 
public class PublisherSvc {
	
	private static final String HEADER_TOKEN_NAME = "arenal-token";
	
	//Helper function
	@SuppressWarnings("unchecked")
	private List<VendorToken> getVendorTokens(Session session, String complexToken){
		
		if (!complexToken.matches("^\\d+-[\\dA-Za-z]+$")) {return null;}
		String[] parts = complexToken.split("-");
		Integer tokenID = Integer.parseInt(parts[0]);
		String token = parts[1];
		
		return session.createCriteria(VendorToken.class)
				.add(Restrictions.idEq(tokenID))
				.add(Restrictions.eq("token", token))
				.add(Restrictions.le("validFrom", new Date()))
			 	.add(Restrictions.disjunction()
					 .add(Restrictions.isNull("validTo"))
					 .add(Restrictions.ge("validTo", new Date())))
				.setMaxResults(1)
				.list();

	}
	
	//POST=Insert
	@POST
	@Produces(MediaType.APPLICATION_XML + ";charset=utf-8")
	public Publisher registerPublisher(
			@HeaderParam(HEADER_TOKEN_NAME) @DefaultValue("") String vendorToken, 
			@Context HttpServletRequest request,
			Publisher cltPublisher) throws ArenalException {
		
		//Validate token
		if(vendorToken == null || vendorToken.isEmpty() || !vendorToken.matches("^\\d+-[\\dA-Za-z]+$")) {
			//No token provided or incorrect format
			throw new ArenalException(Status.UNAUTHORIZED, new ExcetionInfo(ErrorCodes.INVALID_COMPLEX_TOKEN));}
		
		//Check if there is data to insert
		if (cltPublisher == null) {
			//No data have been sent
			throw new ArenalException(Status.BAD_REQUEST, new ExcetionInfo(ErrorCodes.NO_DATA_PROVIDED));}
		
		//Validate data
		Set<ConstraintViolation<Publisher>> constraintViolations = 
				Validation.buildDefaultValidatorFactory().getValidator().validate(cltPublisher, InsertOnlyChecks.class);
		constraintViolations.addAll(Validation.buildDefaultValidatorFactory().getValidator().validate(cltPublisher));
		if (constraintViolations.size() > 0) {
			//Object isn't valid
			throw new ArenalException(
					new ExcetionInfo(
							ErrorCodes.INVALID_DATA,
							constraintViolations.iterator().next().getMessage()));
		}
		
		//Open session
		Session session;
		try {
			session = HibernateUtil.getSessionFactory().openSession();
		} catch (Exception e) {
			e.printStackTrace();
			throw new ArenalException(Status.INTERNAL_SERVER_ERROR, new ExcetionInfo(ErrorCodes.DB_EXCEPTION, e.getLocalizedMessage()));
		}
		
		//Check Vendor's tokens
		List<VendorToken> vendorTokens;
		try {
			vendorTokens = getVendorTokens(session, vendorToken);
		} catch (Exception e) {
			//Error receiving vendor's token
			e.printStackTrace();
			try { if (session != null && session.isOpen()) {session.close();} } catch (Exception ex) {/* Nothing to do here. */};
			throw ArenalException.createFromExceptionOnCommit(e);
		}
		
		if (vendorTokens == null || vendorTokens.size() < 1) {
			//No such vendor's token
			System.out.print("No such software vendor token: ");
			try { if (session != null && session.isOpen()) {session.close();} } catch (Exception ex) {/* Nothing to do here. */};
			throw new ArenalException(Status.UNAUTHORIZED, new ExcetionInfo(ErrorCodes.INVALID_TOKEN_ID));
		}
		
		//Generate token
		//Random random = new Random();
		//char[] buf = new char[10];
		//for (int idx = 0; idx < buf.length; ++idx) 
		//      buf[idx] = (char) ('A' + random.nextInt(26));
		//publ.RegisteredOn = new Date();
		//publ.Token = new String(buf);
	
		//Augment publisher with request data
		cltPublisher.id = null;
		cltPublisher.vendorTokenID = vendorTokens.get(0).id;
		cltPublisher.registeredFrom = request.getRemoteAddr();
		
		//Save publisher
		Transaction trans = null;
		try {
			trans = session.beginTransaction();
			session.save(cltPublisher);
			trans.commit();
		} catch (Exception e) {
			//Error saving data
			e.printStackTrace();
			try {if (trans != null && trans.isActive()) {trans.rollback();}} catch (Exception ex) {/* Nothing to do here. */};
			throw ArenalException.createFromExceptionOnCommit(e);
		} finally {
			try { if (session != null && session.isOpen()) {session.close();} } catch (Exception ex) {/* Nothing to do here. */};
		}
		
		//Send back saved Publisher
		return cltPublisher; 	
		
	}
	
	//GET=Select
	@SuppressWarnings("unchecked")
	@GET
	@Path("/{id:\\d+}")
	@Produces(MediaType.APPLICATION_XML + ";charset=utf-8")
	public Publisher retrievePublisher(
			@PathParam("id") String publisherId,
			@HeaderParam(HEADER_TOKEN_NAME) @DefaultValue("") String token, 
			@Context HttpServletRequest request) throws ArenalException {
		
		if(token == null || token.isEmpty() || !token.matches("^[\\dA-Za-z]+$")) {
			//No token provided
			throw new ArenalException(Status.UNAUTHORIZED, new ExcetionInfo(ErrorCodes.INVALID_TOKEN));}
		if(publisherId == null || publisherId.isEmpty()) {
			//No publisher ID. It shouldn't happen as Jersey will not route such a request
			throw new ArenalException(Status.NOT_FOUND, new ExcetionInfo(ErrorCodes.NO_SUCH_RESOURCE));}
		
		Integer intPublisherId;
		try {
			intPublisherId = Integer.parseInt(publisherId);
		} catch (Exception e) {
			//Can't parse integer
			throw new ArenalException(Status.NOT_FOUND, new ExcetionInfo(ErrorCodes.NO_SUCH_RESOURCE));
		}
		
		Session s = null;
		List<Publisher> res = null;
		try {
			s = HibernateUtil.getSessionFactory().openSession();
			res = s.createCriteria(Publisher.class)
				.add(Restrictions.idEq(intPublisherId))
				.add(Restrictions.eq("token", token))
				.setMaxResults(1)
				.list();
		} catch (Exception e) {
			//DB error
			e.printStackTrace();
			throw ArenalException.createFromExceptionOnCommit(e);
		} finally {
			try { if (s != null && s.isOpen()) {s.close();} } catch (Exception ex) {/* Nothing to do here. */};
		}
		
		if (res != null && res.size() > 0) {
			return res.get(0);
		} else {
			//No such Publisher or more than one
			throw new ArenalException(Status.UNAUTHORIZED, new ExcetionInfo(ErrorCodes.INVALID_TOKEN_ID));
		}

	}
	
	//PUT=Update
	@PUT
	@Path("/{id:\\d+}")
	@Produces(MediaType.APPLICATION_XML + ";charset=utf-8")
	public Publisher updatePublisher(
			@PathParam("id") String publisherId,
			@HeaderParam(HEADER_TOKEN_NAME) @DefaultValue("") String token, 
			@Context HttpServletRequest request,
			Publisher cltPublisher) throws ArenalException {
		
		if (cltPublisher == null) {
			//No data have been sent
			throw new ArenalException(Status.BAD_REQUEST, new ExcetionInfo(ErrorCodes.NO_DATA_PROVIDED));}
		
		//Validate data
		Set<ConstraintViolation<Publisher>> constraintViolations = 
				Validation.buildDefaultValidatorFactory().getValidator().validate(cltPublisher);
		if (constraintViolations.size() > 0) {
			//Object isn't valid
			throw new ArenalException(
					new ExcetionInfo(
							ErrorCodes.INVALID_DATA,
							constraintViolations.iterator().next().getMessage()));
		}
		
		//Following throws proper exceptions itself
		Publisher dbPublisher = this.retrievePublisher(publisherId, token, request);
		
		//Copy properties
		dbPublisher.copyMutablePropertiesFrom(cltPublisher);
				
		Session s = null;
		Transaction t = null;
		try {
			s = HibernateUtil.getSessionFactory().openSession();
			t = s.beginTransaction();
			s.update(dbPublisher);
			t.commit();
		} catch (Exception e) {
			//Can't commit
			e.printStackTrace();
			try {if (t != null && t.isActive()) {t.rollback();}} catch (Exception ex) {/* Nothing to do here. */};
			throw ArenalException.createFromExceptionOnCommit(e);
		} finally {
			try { if (s != null && s.isOpen()) {s.close();} } catch (Exception ex) {/* Nothing to do here. */};
		}
		
		//Send back updated publisher
		return dbPublisher;
	
	}
		
	//DELETE=Delete (Invalidate)
	@DELETE
	@Path("/{id:\\d+}")
	@Produces(MediaType.APPLICATION_XML + ";charset=utf-8")
	public Publisher invalidatePublisher(
			@PathParam("id") String publisherId,
			@HeaderParam(HEADER_TOKEN_NAME) @DefaultValue("") String token, 
			@Context HttpServletRequest request) throws ArenalException {
			
		if(token == null || token.isEmpty()) {
			//No token provided
			throw new ArenalException(Status.UNAUTHORIZED, new ExcetionInfo(ErrorCodes.INVALID_TOKEN));}
		if(publisherId == null || publisherId.isEmpty()) {
			//No publisher ID. It shouldn't happen as Jersey will not route such a request
			throw new ArenalException(Status.NOT_FOUND, new ExcetionInfo(ErrorCodes.NO_SUCH_RESOURCE));}
			
		Publisher dbPublisher = this.retrievePublisher(publisherId, token, request);
		
		dbPublisher.validTrough = new Date();
		dbPublisher.canceledBy = "self";

		Session s = null;
		Transaction t = null;
		try {
			s = HibernateUtil.getSessionFactory().openSession();
			t = s.beginTransaction();
			s.update(dbPublisher);
			t.commit();
		} catch (Exception e) {
			//Can't commit 
			e.printStackTrace();
			try {if (t != null && t.isActive()) {t.rollback();}} catch (Exception ex) {/* Nothing to do here. */};
			throw new ArenalException(Status.INTERNAL_SERVER_ERROR, new ExcetionInfo(ErrorCodes.DB_EXCEPTION, e.getLocalizedMessage()));
		} finally {
			try { if (s != null && s.isOpen()) {s.close();} } catch (Exception ex) {/* Nothing to do here. */};
		}
		
		//Send back invalidated publisher
		return dbPublisher;
		
	}
	
}
